// Copyright 2018 The Go Authors. All rights reserved.
// Use of this session code is governed by a BSD-style
// license that can be found in the LICENSE file.

package lsp

import (
	"context"
	"fmt"
	"mi/internal/event"
	"mi/internal/lsp/protocol"
	"mi/internal/lsp/session"
	"mi/internal/pinyin"
	"mi/internal/span"
	"mi/internal/xpath"
	"strings"
)

func (s *Server) completion(ctx context.Context, params *protocol.CompletionParams) (*protocol.CompletionList, error) {
	data, rgn, err := s.session.GetCompletionsAtPosition(ctx,
		xpath.URI(params.TextDocument.URI), toUTF16Position(params.Position))
	if err != nil {
		event.Error(ctx, err.Error(), err)
		return nil, nil
	}

	items := toProtocolCompletionItems(data, toProtocolRange(rgn), &session.Options{})
	return &protocol.CompletionList{
		IsIncomplete: false,
		Items:        items,
	}, nil
}

func toUTF16Position(position protocol.Position) span.UTF16Position {
	return span.UTF16Position{
		Line:      int(position.Line),
		Column: int(position.Character),
	}
}

func toUTF16Span(rgn protocol.Range) span.UTF16Span {
	return span.UTF16Span{
		Start: toUTF16Position(rgn.Start),
		End:   toUTF16Position(rgn.End),
	}
}

func toProtocolPosition(position span.UTF16Position) protocol.Position {
	return protocol.Position{
		Line:      float64(position.Line),
		Character: float64(position.Column),
	}
}

func toProtocolRange(rgn span.UTF16Span) protocol.Range {
	return protocol.Range{
		Start: toProtocolPosition(rgn.Start),
		End:   toProtocolPosition(rgn.End),
	}
}

func toProtocolCompletionItems(candidates []session.CompletionItem, rng protocol.Range, options *session.Options) []protocol.CompletionItem {
	var (
		items                  = make([]protocol.CompletionItem, 0, len(candidates))
		numDeepCompletionsSeen int
	)
	for i, candidate := range candidates {
		// Limit the number of deep completions to not overwhelm the user in cases
		// with dozens of deep completion matches.
		if candidate.Depth > 0 {
			if !options.DeepCompletion {
				continue
			}
			if numDeepCompletionsSeen >= 100 {
				continue
			}
			numDeepCompletionsSeen++
		}
		insertText := candidate.InsertText
		if options.InsertTextFormat == protocol.SnippetTextFormat {
			insertText = candidate.Snippet()
		}

		// This can happen if the client has snippets disabled but the
		// candidate only supports snippet insertion.
		if insertText == "" {
			continue
		}

		item := protocol.CompletionItem{
			Label:  candidate.Label,
			Detail: candidate.Detail,
			Kind:   convertKind(candidate.Kind),
			TextEdit: &protocol.TextEdit{
				NewText: insertText,
				Range:   rng,
			},
			InsertTextFormat:    options.InsertTextFormat,
			AdditionalTextEdits: candidate.AdditionalTextEdits,
			// This is a hack so that the client sorts completion results in the order
			// according to their score. This can be removed upon the resolution of
			// https://github.com/Microsoft/language-server-protocol/issues/348.
			SortText: fmt.Sprintf("%05d", i),

			// Trim operators (VSCode doesn't like weird characters in
			// filterText).
			FilterText: getPingYin(strings.TrimLeft(candidate.InsertText, "&*")),//strings.TrimLeft(candidate.InsertText, "&*"),

			Preselect:     i == 0,
			Documentation: candidate.Documentation,
		}
		items = append(items, item)
	}
	return items
}

func convertKind(kind session.ScriptElementKind) protocol.CompletionItemKind {
	switch kind {
	case session.SEKPrimitiveType,
	 session.SEKKeyword:
		return protocol.KeywordCompletion

	case session.SEKVariableElement,
	 session.SEKLocalVariableElement,
	 session.SEKAlias,
	 session.SEKParameterElement:
		return protocol.VariableCompletion

	case session.SEKMemberVariableElement:
		return protocol.FieldCompletion

	case session.SEKFunctionElement,
	 session.SEKLocalFunctionElement:
		return protocol.FunctionCompletion

	case session.SEKMemberFunctionElement,
	session.SEKConstructSignatureElement,
	 session.SEKCallSignatureElement,
	 session.SEKIndexSignatureElement:
		return protocol.MethodCompletion

	case session.SEKEnumElement:
		return protocol.EnumCompletion

	case session.SEKEnumMemberElement:
		return protocol.EnumMemberCompletion

	case session.SEKModuleElement,
	 session.SEKExternalModuleName:
		return protocol.ModuleCompletion

	case session.SEKClassElement,
	 session.SEKTypeParameterElement:
		return protocol.ClassCompletion

	case session.SEKInterfaceElement:
		return protocol.InterfaceCompletion

	case session.SEKWarning:
		return protocol.TextCompletion

	case session.SEKScriptElement:
		return protocol.FileCompletion

	case session.SEKDirectory:
		return protocol.FolderCompletion

	default:
		return protocol.PropertyCompletion
	}
}

func getPingYin(text string) string {
	str, _ := pinyin.New(text).Split("").Mode(pinyin.InitialsInCapitals).Convert()
	return str
}